#include <iostream>

#include "A51Cuda.h"
#include "A51CudaSlice.h"

A51Cuda::A51Cuda(uint32_t max_rounds, int condition)
{
    mRunning = true;
    mMaxRound = max_rounds;
    mCondition = condition;
    processThread = new boost::thread(boost::bind(&A51Cuda::Process, this));
}

A51Cuda::~A51Cuda()
{
    mRunning = false;
    processThread->join();
}



int A51Cuda::Submit(
        uint64_t start_value,
        unsigned int start_round,
        uint32_t advance,
        void* context
        )
{
    mMutex.lock();
    mInputStart.push_back(start_value);
    mInputRoundStart.push_back(start_round);
    mInputRoundStop.push_back(mMaxRound);
    mInputContext.push_back(context);
    mInputAdvance.push_back(advance);
    int size = mInputRoundStart.size();

    mMutex.unlock();
    return size;
}

int A51Cuda::SubmitPartial(
        uint64_t start_value,
        unsigned int stop_round,
        uint32_t advance,
        void* context)
{
    mMutex.lock();

    mInputStart.push_front(start_value);
    mInputRoundStart.push_front(0);
    mInputRoundStop.push_front(stop_round);
    mInputContext.push_front(context);
    mInputAdvance.push_front(advance);
    int size = mInputRoundStart.size();

    mMutex.unlock();

    return size;
}

void A51Cuda::Process()
{
    A51CudaSlice* slice = new A51CudaSlice(this, 0, mCondition, mMaxRound);

    for (;;)
    {
        mMutex.lock();
        int available = mInputStart.size();
        mMutex.unlock();

        slice->tick();
        if (!mRunning)
            break;
    }
    delete slice;
}

bool A51Cuda::PopRequest(JobPiece_s* job)
{

    bool res = false;
    mMutex.lock();
    if (mInputStart.size()>0) {
        res = true;
        job->start_value = mInputStart.front();
        mInputStart.pop_front();
        job->start_round = mInputRoundStart.front();
        mInputRoundStart.pop_front();
        job->end_round = mInputRoundStop.front()-1;
        mInputRoundStop.pop_front();
        job->current_round = job->start_round;
        job->context = mInputContext.front();
        mInputContext.pop_front();
        unsigned int advance = mInputAdvance.front();
        mInputAdvance.pop_front();

        Advance* pAdv;
        map<uint32_t,Advance*>::iterator it = mAdvanceMap.find(advance);
        if (it==mAdvanceMap.end()) {
            pAdv = new Advance(advance,mMaxRound);
            mAdvanceMap[advance]=pAdv;
        } else {
            pAdv = (*it).second;
        }
        job->round_func = pAdv->getAdvances();
        job->cycles = 0;
        job->idle = false;
    }
    mMutex.unlock();
    return res;
}

bool A51Cuda::PushResult(JobPiece_s* job)
{
    mMutex.lock();
    mOutput.push( pair<uint64_t,uint64_t>(job->start_value, job->end_value) );
    mMutex.unlock();
    return true;
}



bool A51Cuda::PopResult(uint64_t& start_value, uint64_t& stop_value)//, uint32_t& start_round, uint32_t& stop_round, void** context)
{
    bool res = false;

    mMutex.lock();

    if (mOutput.size() > 0) {
        res = true;
        start_value = mOutput.front().first;
        stop_value = mOutput.front().second;
        mOutput.pop();
    }

    mMutex.unlock();

    return res;
}

